package dse;

import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeSet;

import dse.messages.*;

class Records implements Comparable<Records>{
	DataObject dobj;
	int fuCount;
	int suCount;
	double fuLatency;
	double suLatency;
	
	int frCount;
	int srCount;
	double frLatency;
	double srLatency;
	int frInOneRound;
	
	public Records(DataObject dobj){
		this.dobj = dobj;
	}
	
	public int compareTo(Records o){
		return (fuCount+suCount+frCount+srCount)-(o.fuCount+o.suCount+o.frCount+o.srCount);
	}
}

public class StandardExperimentRecorder implements ExperimentRecorder {
	private Experiment experiment;
	
	private boolean printDebugMessages;
	private boolean printByteTransferMessages;
	private boolean printMessageTransferMessages;
	private boolean printDataRequestMessages;
	//private boolean printBloomFilterRefreshMessages;
	//private boolean printUpdatesForCachingRequestMessages;
	//private boolean printCachingRequestMessages;
	private boolean printDataUpdatesMessages;
	private boolean printSvrMessages;
	
	//private double currentTime;
	private double periodLength;
	private Map<Integer,Double> messageStartTimes;
	private Map<Integer,Double> transactionStartTimes;
	private int periodsRan;
	//private static final int maxID = 5575;
	
	
	/**
	 * The following fields are for tracking the bytes sent for different 
	 * types of requests. 
	 */
	
	private static final int TotalBytesForEverything = 0;
	private static final int TotalBytesForDataObjectRequests = 1;
	private static final int TotalBytesForDataObjectUpdates = 2;
	/*private static final int TotalBytesForBloomFilterRefreshs = 2;
	private static final int TotalBytesForCaching = 3;
	private static final int TotalBytesForDataObjectRequestInfrastructure = 2;
	private static final int TotalBytesForCacheRequestUpdate = 3;
	private static final int TotalBytesForCacheDataTransfer = 4;*/
	private static final int TotalBytesForSVR = 3;
	private double [] bytesSent;
	
	/** The sum of all distances traveled by all bytes. */ 
	private double byteDistTravelled;
	
	/** Total number of messages sent. */
	private int messageCount;
	/** Number of messages that failed. */
	private int failedMessageCount;
	/** Number of messages that were canceled. */
	private int cancelledMessageCount;
	/** Total distance all messages traveled. */
	private double messageDistances;
	/** Total size of all messages sent. */
	private double messageSizes;
	/** Total latency of all messages. */
	private double messageLatencies;
	
	/** Total number of all data requests. */
	private int dataRequestsCount;
	/** Total number of all data requests. */
	private int dataRequestsSuccessCount;
	/** Total number of all data requests. */
	private int dataRequestsFailureCount;
	/** Total time all data requests took. */
	private double totalTimeForDataRequests;
	/** Total time all successful data requests took. */
	private double totalTimeForSuccessfulDataRequests;
	/** Total Total time all failed data requests took. */
	private double totalTimeForFailedDataRequests;
	/** Total size of all data objects requested. */
	private double totalDataObjectSizes;
	/** Distances successful data objects were from requester */
	private double totalDistancesFromRequesterToDataObject;
	/** Number of data objects found cached. */
	//private int dataObjectsFoundCached;	
	
	/** Share lookups that succeeded. */
	private int successfulShareLookups;
	/** Share lookups that failed. */
	private int failedShareLookups;
	
	/** Total number of all data requests. */
	//private int bloomFilterRefreshCount;
	/** Total number of all data requests. */
	//private int bloomFilterRefreshSuccessCount;
	/** Total number of all data requests. */
	//private int bloomFilterRefreshFailureCount;
	
	/** Total number of all data requests. */
	//private int cacheRequestCount;
	/** Total number of all data requests. */
	//private int cacheRequestSuccessCount;
	/** Total number of all data requests. */
	//private int cacheRequestFailureCount;
	/** Number of such transactions. */
	//private int updateSuperNodeAboutRequestsCount;
	/** Size of all cached objects. */
	//private double totalCachedObjectSizes;
	
	//Add new for update operation
	/** Total number of all data updates. */
	private int dataUpdatesCount;
	/** Total number of all data updates. */
	private int dataUpdatesSuccessCount;
	/** Total number of all data updates. */
	private int dataUpdatesFailureCount;
	/** Total time all data updates took. */
	private double totalTimeForDataUpdates;
	/** Total time all successful data updates took. */
	private double totalTimeForSuccessfulDataUpdates;
	/** Total Total time all failed data updates took. */
	private double totalTimeForFailedDataUpdates;
	
	private int svrCount;
	private int svrFinishCount;
	private double totalTimeForSvr;
	
	//Add for request more shares and previous versions;
	private int tranxNumberRequestMoreShares;
	private int tranxNumberRequestPreviousShares;
	private int totalTimesForRequestMoreShares;
	private int totalTimesForRequestPreviousShares;
	private int tranxNumberRequestOneRound;
	private double totalTimeForOneRound;
	private double totalTimeForMoreRound;
	
	private int tranxNumberForOneInMoreRound;
	private double totalTimeForOneInMoreRound;
	private LinkedList<Node> clientOneRound;
	private LinkedList<Node> clientMoreRound;
	
	//New add;
	private double rDuration;
	private double uDuration;
	
	private int shareHistorySize;
	private Hashtable<DataObject, Records> records; 
	
	public StandardExperimentRecorder (Map<String,String> settings) {
		configure(settings);
		
		// Setup other variables
		messageStartTimes = new Hashtable<Integer, Double>();
		transactionStartTimes = new Hashtable<Integer, Double>();
		periodsRan = 0;
		
		bytesSent = new double [4];
		
		byteDistTravelled = 0;
		dataRequestsCount = 0;
		dataRequestsSuccessCount = 0;
		dataRequestsFailureCount = 0;
		totalTimeForDataRequests = 0;
		totalTimeForSuccessfulDataRequests = 0;
		totalTimeForFailedDataRequests = 0;
		totalDataObjectSizes = 0;
		totalDistancesFromRequesterToDataObject = 0;
		//dataObjectsFoundCached = 0;
		messageCount = 0;
		messageDistances = 0;
		messageSizes = 0;
		messageLatencies = 0;
		
		failedMessageCount = 0;
		cancelledMessageCount = 0;
		
		successfulShareLookups = 0;
		failedShareLookups = 0;
		
		//bloomFilterRefreshCount = 0;
		//bloomFilterRefreshSuccessCount = 0;
		//bloomFilterRefreshFailureCount = 0;
		
		/*cacheRequestCount = 0;
		cacheRequestSuccessCount = 0;
		cacheRequestFailureCount = 0;
		
		updateSuperNodeAboutRequestsCount = 0;
		totalCachedObjectSizes = 0;*/
		
		dataUpdatesCount = 0;
		dataUpdatesSuccessCount = 0;
		dataUpdatesFailureCount = 0;
		totalTimeForDataUpdates = 0;
		totalTimeForSuccessfulDataUpdates = 0;
		totalTimeForFailedDataUpdates = 0;
		tranxNumberRequestMoreShares = 0;
		tranxNumberRequestPreviousShares = 0;
		totalTimesForRequestMoreShares = 0;
		totalTimesForRequestPreviousShares = 0;
		totalTimeForOneRound = 0;
		totalTimeForMoreRound = 0;
		tranxNumberForOneInMoreRound = 0;
		totalTimeForOneInMoreRound = 0;
		clientOneRound = new LinkedList<Node>();
		clientMoreRound = new LinkedList<Node>();
		rDuration = 0;
		uDuration = 0;
		shareHistorySize = 0;
		
		records = new Hashtable<DataObject, Records>();
		
		this.TLUpdateStartTimes = new Hashtable<Integer, Double>();
		this.TLUpdatePropagationCount = 0;
		this.totalTimeForTLUpdatePropagation = 0;
		
		svrCount = 0;
		totalTimeForSvr = 0;
	}
	
	public void setExperiment(Experiment exp){
		this.experiment = exp;
	}

	private void configure (Map<String, String> settings) {
		// Configure debug messages...
		if (settings.containsKey("print.*") 
			&& settings.get("print.*").equals("true"))
				printDebugMessages = printByteTransferMessages = 
					printMessageTransferMessages = 
						printDataRequestMessages = true;
		if (settings.containsKey("print.debug")) 
			printDebugMessages = settings.get("print.debug").equals("true");
		if (settings.containsKey("print.bytes")) 
			printByteTransferMessages = 
				settings.get("print.bytes").equals("true");
		if (settings.containsKey("print.transfers")) 
			printMessageTransferMessages = 
				settings.get("print.transfers").equals("true");
		if (settings.containsKey("print.data-requests")) 
			printDataRequestMessages = 
				settings.get("print.data-requests").equals("true");
		//if (settings.containsKey("print.bloom-filter-refreshes")) 
			//printBloomFilterRefreshMessages = 
				//settings.get("print.bloom-filter-refreshes").equals("true");
		//if (settings.containsKey("print.updates-for-caching")) 
		//	printUpdatesForCachingRequestMessages = 
		//		settings.get("print.updates-for-caching").equals("true");
		//if (settings.containsKey("print.caching")) 
		//	printCachingRequestMessages = 
		//		settings.get("print.caching").equals("true");
		if (settings.containsKey("print.data-updates"))
			printDataUpdatesMessages = 
				settings.get("print.data-updates").equals("true");
		if(settings.containsKey("print.svr"))
			printSvrMessages = settings.get("print.svr").equals("true");
	}
			
	public void bloomFilterRefreshInitiated(
			int id,
			Node requester, 
			Node target, 
			double timeLeft
			)
	{
		/*double time = currentTime + 
			((timeLeft == ExperimentRecorder.PeriodStart)?
				(0):(periodLength - timeLeft));
	
		transactionStartTimes.put(id, time);
		
		bloomFilterRefreshCount++;
		
		if (printBloomFilterRefreshMessages)
			System.out.println(
					"bloomFilterRefreshInitiated:" +   
					" trans# " + id + " from " + requester +
					" at time " + time 
					);*/
	}
	public void bloomFilterRefreshFinished(
			int id,
			Node requester, 
			Node target, 
			boolean success, 
			double timeLeft
			)
	{
		/*double time = currentTime + 
			((timeLeft == ExperimentRecorder.PeriodStart)?
					(0):(periodLength - timeLeft));

		double startTime = transactionStartTimes.get(id);
		transactionStartTimes.remove(id);
		
		if (success)
			bloomFilterRefreshSuccessCount++;
		else
			bloomFilterRefreshFailureCount++;
		
		if (printBloomFilterRefreshMessages)
			System.out.println(
					"bloomFilterRefresh" + ((success)?("Finished"):("Failed")) +   
					" trans#" + id + " " + requester +
					" in time: " + (time-startTime)
					);*/
	}
	public void cacheRequestInitiated (
			int id,
			Node requester, 
			DataObject object, 
			double timeLeft
			)
	{
		/*double time = currentTime + 
			((timeLeft == ExperimentRecorder.PeriodStart)?
				(0):(periodLength - timeLeft));
	
		transactionStartTimes.put(id, time);
		
		cacheRequestCount++;
		
		if (printCachingRequestMessages)
			System.out.println(
					"cacheRequestInitiated:" +   
					" trans# " + id + " from " + requester +
					" for dobj " + object.id() + " (at time " + time + ")"
					);*/
	}
	public void cacheRequestFinished (
			int id, 
			Node requester, 
			DataObject object, 
			int sharesCached,
			boolean success, double timeLeft
			)
	{
		/*double time = currentTime + 
		((timeLeft == ExperimentRecorder.PeriodStart)?
			(0):(periodLength - timeLeft));

		double startTime = transactionStartTimes.get(id);
		transactionStartTimes.remove(id);

		if (success) {
			if (sharesCached == -1)
				totalCachedObjectSizes += object.size();
			else
				totalCachedObjectSizes += object.shareSize() * sharesCached;
			cacheRequestSuccessCount++;
		} else
			cacheRequestFailureCount++;
		
		if (printCachingRequestMessages)
			System.out.println(
					"cacheRequest" + ((success)?("Finished"):("Failed")) +   
					" trans#" + id + " " + requester + " for " + object +
					" in time: " + (time-startTime)
					);*/
	}
	
	public void bytesSent (
			int id, MessageType messageType, MessagePurpose purpose,
			double distance, long bytes
			)
	{
		byteDistTravelled += distance*bytes;
		bytesSent[TotalBytesForEverything] += bytes;		
		bytesSent[MessagePurpose.toInt(purpose)+1] += bytes;
		/*if (purpose == MessagePurpose.DataObjectRequest ||
				purpose == MessagePurpose.DataObjectUpdate) {
			if (messageType != MessageType.ShareDataTransfer &&
					messageType != MessageType.DataObjectTransfer &&
					messageType != MessageType.ShareDownload &&
					messageType != MessageType.ShareUpdate &&
					messageType != MessageType.ShareUpdateV2 &&
					messageType != MessageType.ShareUpdateV3 &&
					messageType != MessageType.ShareUpdateV4)
				bytesSent[TotalBytesForDataObjectRequestInfrastructure] 
				          += bytes;
		} else if (purpose == MessagePurpose.Caching) {
			if (messageType == MessageType.ShareDataTransfer)
				bytesSent[TotalBytesForCacheDataTransfer] += bytes;
			else if (messageType == MessageType.UpdateOnRecentLocalRequests)
				bytesSent[TotalBytesForCacheRequestUpdate] += bytes;
		} 
		else if (purpose == MessagePurpose.ResumeFromFailure) {
				bytesSent[TotalBytesForDataObjectRequestInfrastructure] 
				          += bytes;
		}*/
		
		if (printByteTransferMessages)
			System.out.println(
					"transfer: msg#" + id + " (" + messageType.toString() +  
					") " + bytes + "b over " + distance + " miles."
					);
	}
	
	public void bytesSent (
			int id, MessageType messageType, MessagePurpose purpose,
			double distance, long bytes, long bytesRemaining
			)
	{
		byteDistTravelled += distance*bytes;
		bytesSent[TotalBytesForEverything] += bytes;		
		bytesSent[MessagePurpose.toInt(purpose)+1] += bytes;
		/*if (purpose == MessagePurpose.DataObjectRequest ||
				purpose == MessagePurpose.DataObjectUpdate) {
			if (messageType != MessageType.ShareDataTransfer &&
					messageType != MessageType.DataObjectTransfer)
				bytesSent[TotalBytesForDataObjectRequestInfrastructure] 
				          += bytes;
		} else if (purpose == MessagePurpose.Caching) {
			if (messageType == MessageType.ShareDataTransfer)
				bytesSent[TotalBytesForCacheDataTransfer] += bytes;
			else if (messageType == MessageType.UpdateOnRecentLocalRequests)
				bytesSent[TotalBytesForCacheRequestUpdate] += bytes;
		} else if (purpose == MessagePurpose.ResumeFromFailure) {
			bytesSent[TotalBytesForDataObjectRequestInfrastructure] 
			          += bytes;
		}*/
		
		if (printByteTransferMessages)
			System.out.println(
					"transfer: msg#" + id + " (" + messageType.toString() + ") " 
					+ bytes + "b (w/ " + bytesRemaining + "b remaining) over " + 
					distance + " miles."
					);
	}
		
	public void dataRequestFinished (
			int id, 
			Node requester, 
			DataObject object,
			boolean success, 
			Node foundInOrNear, 
			boolean moreRound,
			double time
			)
	{
		//double time = currentTime + 
			//((timeLeft == ExperimentRecorder.PeriodStart)?
				//(0):(periodLength - timeLeft));
	
		double startTime = transactionStartTimes.get(id);
		transactionStartTimes.remove(id);
		
		if(startTime >= 3){
			double tt = (time-startTime);
			totalTimeForDataRequests += tt;
			if(!records.containsKey(object))
				records.put(object, new Records(object));
			
			if (success) {
				dataRequestsSuccessCount++;
				totalDistancesFromRequesterToDataObject += requester.distanceTo(foundInOrNear);
				totalTimeForSuccessfulDataRequests += tt;
				records.get(object).srCount++;
				records.get(object).srLatency += tt;
			
				if (moreRound){
					totalTimeForMoreRound += tt;
					this.tranxNumberRequestPreviousShares++;
					this.clientMoreRound.add(requester);
				}
				else{
					totalTimeForOneRound += tt;
					this.tranxNumberRequestOneRound++;
					this.clientOneRound.add(requester);
				}
			} else {
				dataRequestsFailureCount++;
				totalTimeForFailedDataRequests += tt;
				records.get(object).frCount++;
				records.get(object).frLatency += tt;
			}
		
			if (printDataRequestMessages)
				System.out.println(
					"request" + ((success)?("Finished"):("Failed")) +   
					" trans#" + id + " " + requester +
					" for " + object + " in time: " + (time-startTime)
					);
		}
	}
	
	public void dataRequestInitiated (
			int id,
			Node requester, 
			DataObject object, 
			double time
			)
	{
		//double time = currentTime + 
			//((timeLeft == ExperimentRecorder.PeriodStart)?
				//(0):(periodLength - timeLeft));
		
		transactionStartTimes.put(id, time);
		
		if(time >= 3){
			dataRequestsCount++;			
			totalDataObjectSizes += object.size();
		
			if (printDataRequestMessages)
				System.out.println(
					"requestInitiated:" +   
					" trans# " + id + " from " + requester +
					" for dobj " + object.id() + " (at time " + time + ")"
					);
		}
	}
	
	public void OneRoundInMoreFinished (int id, double time)
	{
		double startTime = transactionStartTimes.get(id);
		
		if(startTime >= 3){
			double tt = (time-startTime);
			totalTimeForDataRequests += tt;
			this.tranxNumberForOneInMoreRound++;
			this.totalTimeForOneInMoreRound += tt;
		}
	}
	
	public void dataUpdateFinished (
			int id, 
			Node requester, 
			DataObject object,
			boolean success, 
			//Node foundInOrNear, 
			double time
			)
	{
		//double time = currentTime + 
		//	((timeLeft == ExperimentRecorder.PeriodStart)?
		//		(0):(periodLength - timeLeft));
	
		double startTime = transactionStartTimes.get(id);
		transactionStartTimes.remove(id);
		if(startTime >= 3){
			double tt = (time-startTime);
			totalTimeForDataUpdates += tt;
			if(!records.containsKey(object))
				records.put(object, new Records(object));
			
			if (success) {
				dataUpdatesSuccessCount++;
				//totalDistancesFromRequesterToDataObject += 
					//requester.distanceTo(foundInOrNear);
				totalTimeForSuccessfulDataUpdates += tt;
				
				records.get(object).suCount++;
				records.get(object).suLatency += tt;
			} else {
				dataUpdatesFailureCount++;
				totalTimeForFailedDataUpdates += tt;
								
				records.get(object).fuCount++;
				records.get(object).fuLatency += tt;
			}
			
			if (printDataUpdatesMessages)
				System.out.println(
						"Update" + ((success)?("Finished"):("Failed")) +   
						" trans#" + id + " " + requester +
						" for " + object + " in time: " + (time-startTime)
						);
		}
	}
	public void dataUpdateInitiated (
			int id,
			Node requester, 
			DataObject object, 
			double time
			)
	{
		/*double time = currentTime + 
			((timeLeft == ExperimentRecorder.PeriodStart)?
				(0):(periodLength - timeLeft));*/
		
		transactionStartTimes.put(id, time);
		if(time >=3 ){
			dataUpdatesCount++;
			totalDataObjectSizes += object.size();
			
			if (printDataUpdatesMessages)
				System.out.println(
						"updateInitiated:" +   
						" trans# " + id + " from " + requester +
						" for dobj " + object.id() + " (at time " + time + ")"
						);
		}
	}
	
	public void svrFinished (
			int id, 
			Node requester, 
			DataObject object,
			double time
			)
	{
		double startTime = transactionStartTimes.get(id);
		transactionStartTimes.remove(id);
		
		if(startTime >= 3){
			double tt = (time-startTime);
			totalTimeForSvr += tt;
			svrFinishCount++;
								
			if (printSvrMessages)
				System.out.println(
					"SVR Finished: trans#" + id + " " + requester +
					" for " + object + " in time: " + (time-startTime)
					);
		}
	}
	
	public void svrInitiated (
			int id,
			Node requester, 
			DataObject object, 
			double time
			)
	{
		transactionStartTimes.put(id, time);
		
		if(time >= 3){
			svrCount++;			
		
			if (printSvrMessages)
				System.out.println(
					"SVR Initiated: trans# " + id + " from " + requester +
					" for dobj " + object.id() + " (at time " + time + ")"
					);
		}
	}

	public void debug (Object object, String message, double time) {
		if (printDebugMessages) {
			//if (timeLeft == ExperimentRecorder.PeriodStart)
				//timeLeft = periodLength;
			System.out.println(
					object.getClass().getCanonicalName() + "." + message + 
					" (at " + time + ")"
					);
		}

	}

	public void debug (String message) {
		if (printDebugMessages)
			System.out.println(message);
	}
	
	public ExperimentResults getResults () { 
		// We have 22 methods to set...
//		for(Integer inte : this.transactionStartTimes.keySet())
//		{
//			System.err.println(inte);
//		}
		
		double[] distanceOne = new double[32];
		double[] distanceMore = new double[32];
		DataObject dobj = this.experiment.dataObjects.firstElement();
		for(Node node : clientOneRound){
			LinkedList<Node> servers = MessageUtil.getNodesByDht(this.experiment.network, node, dobj, this.experiment.modules.assigner());
			for(int i=0; i<servers.size(); i++){
				distanceOne[i] += node.distanceTo(servers.get(i));
			}
		}
		for(Node node : clientMoreRound){
			LinkedList<Node> servers = MessageUtil.getNodesByDht(this.experiment.network, node, dobj, this.experiment.modules.assigner());
			for(int i=0; i<servers.size(); i++){
				distanceMore[i] += node.distanceTo(servers.get(i));
			}
		}
		
		for(int i=0; i<distanceOne.length; i++){
			distanceOne[i] = distanceOne[i]/clientOneRound.size();
			double ping = 0.4 + 0.3*Math.pow(distanceOne[i], 0.735);
			distanceOne[i] = (0.02*Math.pow(10064, 0.51)*ping + ping)/1000;
			distanceOne[i] += (0.02*Math.pow(48, 0.51)*ping + ping)/1000;
			System.out.print(distanceOne[i] + "\t");			
		}
		System.out.println();
		for(int i=0; i<distanceMore.length; i++){
			distanceMore[i] = distanceMore[i]/clientMoreRound.size();
			double ping = 0.4 + 0.3*Math.pow(distanceMore[i], 0.735);
			distanceMore[i] = (0.02*Math.pow(10064, 0.51)*ping + ping)/1000;
			distanceMore[i] += (0.02*Math.pow(48, 0.51)*ping + ping)/1000;
			System.out.print(distanceMore[i] + "\t");			
		}
		System.out.println();
		
		
		TreeSet<Records> treemax = new TreeSet<Records>();
		for(Object obj : records.keySet()){
			treemax.add(records.get(obj));
		}
		/*Iterator<Records> itr = treemax.descendingIterator();
		while(itr.hasNext()){
			Records r = itr.next();
			System.out.print(r.dobj.id() + "\t");
			System.out.print(r.srLatency/r.srCount + "\t");
			System.out.print(r.frInOneRound*1.0/(r.srCount+r.frCount) + "\t");
			System.out.println(r.suLatency/r.suCount);
		}*/
		Records max = treemax.last();
		
		ExperimentResults res = new ExperimentResults ();
		res.setAverageByteDistance(
				byteDistTravelled/bytesSent[TotalBytesForEverything]
				);
		res.setAverageDataObjectSize(
				totalDataObjectSizes/dataRequestsCount
				);
		res.setAverageDistanceOfObjectFromRequester(
				totalDistancesFromRequesterToDataObject/dataRequestsSuccessCount
				);
		
		res.setAverageMessageDistance(messageDistances/messageCount);
		res.setAverageMessageLatency(messageLatencies/messageCount);
		res.setAverageMessageSize(messageSizes/messageCount);
		
		res.setAverageRequestTime(totalTimeForDataRequests/dataRequestsCount);
		if (dataRequestsFailureCount == 0) 
			res.setAverageRequestTimeForFailures(0);
		else
			res.setAverageRequestTimeForFailures(
					totalTimeForFailedDataRequests/dataRequestsFailureCount
					);
		if (dataRequestsSuccessCount == 0)
			res.setAverageRequestTimeForSuccesses(0);
		else
			res.setAverageRequestTimeForSuccesses(
					totalTimeForSuccessfulDataRequests/dataRequestsSuccessCount
					);
		res.setAverageMaxRequestTime((max.frLatency+max.srLatency)/(max.srCount+max.frCount));
		if (max.frCount == 0) 
			res.setAverageMaxRequestTimeForFailures(0);
		else
			res.setAverageMaxRequestTimeForFailures(max.frLatency/max.frCount);
		if (max.srCount == 0)
			res.setAverageMaxRequestTimeForSuccesses(0);
		else
			res.setAverageMaxRequestTimeForSuccesses(max.srLatency/max.srCount);
		
		res.setBytesSent(
				bytesSent[TotalBytesForEverything]
				);
		res.setBytesSentForAllDataRequests(bytesSent[TotalBytesForDataObjectRequests]);
		res.setBytesSentForAllDataUpdates(bytesSent[TotalBytesForDataObjectUpdates]);
		res.setBytesSentForAllSvr(bytesSent[TotalBytesForSVR]);
		
		/*res.setBytesSentForAllDataRequestsThatWasNotForDataTransfer(
				bytesSent[TotalBytesForDataObjectRequestInfrastructure]
				);*/
		//res.setDataObjectsFoundCached(dataObjectsFoundCached);
		res.setDataRequests(dataRequestsCount, dataRequestsFailureCount, dataRequestsSuccessCount);
		res.setMaxRequests(max.frCount+max.srCount, max.frCount, max.srCount);
		res.setMessagesSent(messageCount);
		res.setMessagesThatFailed(failedMessageCount);
		res.setMessagesThatWereCancelled(cancelledMessageCount);
		
		res.setNumberOfFailedShareLookups(failedShareLookups);
		res.setNumberOfSuccessfulShareLookups(successfulShareLookups);
		
		/*res.setBloomFilterRefreshes(bloomFilterRefreshCount);
		res.setBloomFilterRefreshesThatSucceeded(
				bloomFilterRefreshSuccessCount
				);
		res.setBloomFilterRefreshesThatFailed(bloomFilterRefreshFailureCount);
		res.setBytesSentForAllBloomFilterRefreshes(
				bytesSent[TotalBytesForBloomFilterRefreshs]
				);
		
		res.setDataObjectCacheRequests(cacheRequestCount);
		res.setDataObjectCacheRequestsThatSucceeded(cacheRequestSuccessCount);
		res.setDataObjectCacheRequestsThatFailed(cacheRequestFailureCount);
		res.setBytesSentForAllDataObjectCaching(
				bytesSent[TotalBytesForCaching]
				);
		res.setBytesSentForDataObjectCachingThatWereRequestUpdates(
				bytesSent[TotalBytesForCacheRequestUpdate]
				);
		res.setBytesSentForDataObjectCachingThatWereForDataTransfer(
				bytesSent[TotalBytesForCacheDataTransfer]
				);
		res.setUpdateSuperNodeAboutRequestsCount(
				updateSuperNodeAboutRequestsCount
				);
		res.setAverageCachedDataObjectSize(
				totalCachedObjectSizes/cacheRequestSuccessCount
				);
				*/
		
		res.setPeriodsRan(periodsRan);
		res.setPeriodTime(periodLength);
		
		
		/**Add***/
		res.setAverageUpdateTime(totalTimeForDataUpdates/dataUpdatesCount);
		if (dataUpdatesFailureCount == 0) 
			res.setAverageUpdateTimeForFailures(0);
		else
			res.setAverageUpdateTimeForFailures(
					totalTimeForFailedDataUpdates/dataUpdatesFailureCount
					);
		if (dataUpdatesSuccessCount == 0)
			res.setAverageUpdateTimeForSuccesses(0);
		else
			res.setAverageUpdateTimeForSuccesses(
					totalTimeForSuccessfulDataUpdates/dataUpdatesSuccessCount
					);
		res.setSvr(svrCount, svrFinishCount);
		res.setAverageSvrTime(totalTimeForSvr/svrFinishCount);
		res.setAverageMaxUpdateTime((max.suLatency+max.fuLatency)/(max.suCount+max.fuCount));
		if (max.fuCount == 0) 
			res.setAverageMaxUpdateTimeForFailures(0);
		else
			res.setAverageMaxUpdateTimeForFailures(max.fuLatency/max.fuCount);
		if (max.suCount == 0)
			res.setAverageMaxUpdateTimeForSuccesses(0);
		else
			res.setAverageMaxUpdateTimeForSuccesses(max.suLatency/max.suCount);
		res.setDataUpdates(dataUpdatesCount, dataUpdatesFailureCount, dataUpdatesSuccessCount);
		res.setMaxUpdates(max.suCount+max.fuCount, max.fuCount, max.suCount);
		res.setRequestMoreShares(this.totalTimesForRequestMoreShares, this.tranxNumberRequestMoreShares);
		res.setRequestPreviousShares(this.totalTimesForRequestPreviousShares, this.tranxNumberRequestPreviousShares, this.tranxNumberRequestOneRound);
		res.setOneInMore(this.tranxNumberForOneInMoreRound, this.totalTimeForOneInMoreRound);
		
		//res.setMaxRequestMoreShares(this.maxTimesForRequestMoreShares, this.maxTranxRequestMoreShares);
		res.setMaxRequestPreviousShares(0, max.frInOneRound);
		res.setTotalTimeForRound(totalTimeForOneRound, totalTimeForMoreRound);
		
		res.setReadUpdateDuration(rDuration, uDuration);
		
		res.setShareHistorySize(shareHistorySize);
		
		res.setTLUpdatePropagation(this.TLUpdatePropagationCount, this.totalTimeForTLUpdatePropagation);
		
		return res;
	}

	public void messageProcessed (
			int id, 
			MessageType messageType
			)
	{
		if (printMessageTransferMessages)
			System.out.println(
					"processing: msg#" + id + " (" + messageType.toString() +")"
					);
	}

	public void messageTransferCancelled (
			int id, MessageType messageType, double time
			)
	{
		double tmt = time - messageStartTimes.get(id);
		
		messageStartTimes.remove(id);		
		messageLatencies += tmt;
		cancelledMessageCount++;
		
		if (printMessageTransferMessages)
			System.out.println(
					"xfer.cancelled: msg#" + id + " (" + messageType.toString()
					+ ")" + " at " + time + ", total msg time = " + tmt
					);
	}
	
	
	public void messageTransferFinished (
			int id, MessageType messageType, double time)
	{
		double tmt = time - messageStartTimes.get(id);
		
		messageStartTimes.remove(id);
		messageLatencies += tmt;
		
		if (printMessageTransferMessages)
			System.out.println(
					"xfer.finished: msg#" + id + " (" + messageType.toString()
					+ ")" + " at " + time + ", total msg time = " + tmt
					);
	}

	public void messageTransferInterupted (
			int id, MessageType messageType, double time)
	{
		double tmt = time - messageStartTimes.get(id);
		
		messageStartTimes.remove(id);
		messageLatencies += tmt;
		failedMessageCount++;

		if (printMessageTransferMessages)
			System.out.println(
					"xfer.interpted: msg#" + id + " (" + messageType.toString()
					+ ")" + " at " + time + ", total msg time = " + tmt
					);
	}
	
	public void messageTransferStarted (
			int id, 
			MessageType messageType, 
			double distance,
			long size,
			double time
			)
	{
		messageStartTimes.put(id, time);
		messageCount++;
		messageDistances += distance;
		messageSizes += size;
		
		if (printMessageTransferMessages)
			System.out.println(
					"xfer.start: msg#" + id + " (" + messageType.toString() +")"
					+ " at " + time
					);
	}

	public void nextPeriod (double startsAt, double length) {
		//this.currentTime = startsAt;
		this.periodLength = length;
		periodsRan++;
	}
	
	public void shareLookupFailed () {
		failedShareLookups++;
	}

	public void shareLookupSuccessful () {
		successfulShareLookups++;
	}

	public void updateSuperNodeWithRequestsFinished (
			int id, Node sender, Node superNode, boolean success, 
			double timeLeft
			)
	{
		/*double time = currentTime + 
			((timeLeft == ExperimentRecorder.PeriodStart)?
				(0):(periodLength - timeLeft));
	
		double startTime = transactionStartTimes.get(id);
		transactionStartTimes.remove(id);
		
		if (printUpdatesForCachingRequestMessages)
			System.out.println(
					"updateSuperNodeWithRequests" + 
					((success)?("Finished"):("Failed")) +   
					" trans#" + id + " " + sender +
					" to " + superNode + " in time: " + (time-startTime)
					);*/
	}
	public void updateSuperNodeWithRequestsInitiated (
			int id, Node sender, Node superNode, double timeLeft
			)
	{
		/*double time = currentTime + 
			((timeLeft == ExperimentRecorder.PeriodStart)?
				(0):(periodLength - timeLeft));
	
		transactionStartTimes.put(id, time);
		
		updateSuperNodeAboutRequestsCount++;
		
		if (printUpdatesForCachingRequestMessages)
			System.out.println(
					"updateSuperNodeWithRequestsInitiated:" +   
					" trans# " + id + " from " + sender +
					" to " + superNode + " (at time " + time + ")"
					);*/
	}
	
	public void warn (String message) {
		System.out.println("WARNING: " + message);
	}
	
	public void requestMoreShares(DataObject dobj, boolean val){
		this.totalTimesForRequestMoreShares++;
		//if(dobj.id() == maxID)
		//	this.maxTimesForRequestMoreShares++;
		if(val){
			this.tranxNumberRequestMoreShares++;
			//if(dobj.id() == maxID)
			//	this.maxTranxRequestMoreShares++;
		}
	}
	public void requestPreviousShares(DataObject dobj, boolean val){
		this.totalTimesForRequestPreviousShares++;
		//if(dobj.id() == maxID)
			//this.maxTimesForRequestPreviousShares++;
		
		if(val){
			this.tranxNumberRequestPreviousShares++;
			//if(dobj.id() == maxID)
				//this.maxTranxRequestPreviousShares++;
			if(records.containsKey(dobj))
				records.get(dobj).frInOneRound++;
		}
	}
	
	
	public void readDuration(double dur){
		rDuration += dur;
	}
	public void updateDuration(double dur){
		uDuration += dur;
	}
	
	public void shareHistorySize(int size){
		if(shareHistorySize < size)
			shareHistorySize = size;
	}
	
	//two level order
	private Map<Integer,Double> TLUpdateStartTimes;
	private int TLUpdatePropagationCount;
	private double totalTimeForTLUpdatePropagation;
	public void dataUpdatePropagationInitiated (
			int id,
			Node requester, 
			DataObject object, 
			double time
			)
	{
		TLUpdateStartTimes.put(id, time);
	}
	public void dataUpdatePropagationFinished (
			int id, 
			Node requester, 
			DataObject object, 
			double time
			)
	{
		double startTime = TLUpdateStartTimes.get(id);
		TLUpdateStartTimes.remove(id);
		if(startTime >= 3){
			double tt = (time-startTime);
			totalTimeForTLUpdatePropagation += tt;
			TLUpdatePropagationCount++;		
			if (printDataUpdatesMessages)
				System.out.println(
						"Update propagation" + "finished" +   
						" trans#" + id + " " + requester +
						" for " + object + " in time: " + (time-startTime)
						);
		}
	}
}